#pragma once

#include <cstdint>
#include <ctime>
#include <memory>
#include <limits>
#include <stdexcept>

#if !defined(__linux__)
#ifndef WIN32_LEAN_AND_MEAN
#   define WIN32_LEAN_AND_MEAN // avoid including unnecessary WIN32 header files
#endif
#   include <windows.h>
#   include <winsock2.h>
#   define ModuleHandle HMODULE // DLL handle
#   define INVALID_MODULE_HANDLE 0
#   define MODULE_SUFFIX ".dll"
#   undef max
#else
#   include <stdio.h>
#   include <string.h>
#   include <stdlib.h>
#   include <dirent.h>
#   include <sys/stat.h>
#   include <unistd.h>
#   include <sys/types.h>
#   include <dlfcn.h>
#   include <arpa/inet.h>
#	define ModuleHandle void* // LINUX SO handle
#   define INVALID_MODULE_HANDLE nullptr
#   define MODULE_SUFFIX ".so"
#   undef max
#endif // !defined(__linux__)

namespace rpc {

class Transport;
class Proxy;
class Stub;
class ProxyCall;
class StubCall;
class RpcService;
class StubCreator;
class Logger;

// RPC error
enum class RpcError : std::uint32_t {
    OK = 1,
    NOT_FOUND,
    EXCEPTION,
    TIMEOUT,
};

// RPC message type
enum class RpcMsgType : std::uint32_t {
    NONE = 0,
    RPC_CALL = 1,
    RPC_RETURN,
    NO_RPC,
};

// RPC service stub type
enum class StubType {
    SINGLE = 1,
    MULTIPLE,
    REENTRANT,
};

enum class StubStatus {
    RESOLVED = 1,
    UPDATING,
};

// RPC timeout
constexpr std::time_t RPC_TIMEOUT = 5000;
// Stream buffer size for single RPC message
constexpr int STREAMBUF_SIZE = 1024 * 512;

using ServiceUUID = std::uint64_t;
using ServiceID = std::uint32_t;
using ProxyID = std::uint32_t;
using MethodID = std::uint32_t;
using CallID = std::uint32_t;
using ErrorID = std::uint32_t;
using MsgTypeID = std::uint32_t;
using StubCallID = std::uint32_t;
using TransportPtr = std::shared_ptr<Transport>;
using ProxyPtr = std::shared_ptr<Proxy>;
using StubPtr = std::shared_ptr<Stub>;
using ProxyCallPtr = std::shared_ptr<ProxyCall>;
using StubCallPtr = std::shared_ptr<StubCall>;
constexpr CallID INVALID_CALL_ID = 0;
constexpr ServiceID INVALID_SERVICE_ID = 0;
constexpr ProxyID INVALID_PROXY_ID = 0;
constexpr MethodID INVALID_METHOD_ID = 0;
constexpr ServiceUUID INVALID_SERVICE_UUID = 0;
constexpr StubCallID INVALID_STUB_CALL_ID = 0;
constexpr std::size_t MAX_SERVICE_COUNT = std::numeric_limits<std::size_t>::max();
using BundleEntryFunc = RpcService*(*)();
constexpr const char* DEFAULT_ENTRY_NAME = "create";
using BundleRegisterFunc = ServiceUUID(*)(rpc::Logger*);
constexpr const char* DEFAULT_REGISTER_NAME = "onRegister";

#if !defined(__linux__)
    #define FuncExport extern "C" __declspec(dllexport) // WIN32 DLL exporter
#else
    #define FuncExport extern "C" // LINUX shared object exporter
#endif // !defined(__linux__)

#define co(expr) coro_start([&](void*){ expr });

class MethodNotFound : public std::runtime_error {
public:
    MethodNotFound(const char* reason) : std::runtime_error(reason) {}
};

class RemoteMethodException : public std::runtime_error {
public:
    RemoteMethodException(const char* reason) : std::runtime_error(reason) {}
};

class RemoteMethodTimeout : public std::runtime_error {
public:
    RemoteMethodTimeout(const char* reason) : std::runtime_error(reason) {}
};

class ProxyCoroMethodException : public std::runtime_error {
public:
    ProxyCoroMethodException(const char* reason) : std::runtime_error(reason) {}
};

inline std::uint64_t htonll(std::uint64_t ui64) {
    return (((std::uint64_t)htonl((std::uint32_t)ui64)) << 32) + htonl(ui64 >> 32); 
}

inline std::uint64_t ntohll(uint64_t ui64) {
    return (((std::uint64_t)ntohl((std::uint32_t)ui64)) << 32) + ntohl(ui64 >> 32); 
}

#if defined(_WIN32) || defined(_WIN64)
    #pragma pack(push)
    #pragma pack(1)
#else
    #pragma pack(1) 
#endif /* defined(_WIN32) || defined(_WIN64) */

struct RpcMsgHeader {
    std::uint32_t length {0};
    MsgTypeID type {0};
    operator bool() const {
        return length != 0;
    }
    void hton() {
        length = htonl(length);
        type = htonl(type);
    }
    void ntoh() {
        length = ntohl(length);
        type = ntohl(type);
    }
};

struct RpcCallRequestHeader {
    ServiceUUID serviceUUID {INVALID_SERVICE_UUID};
    ServiceID serviceID {INVALID_SERVICE_ID};
    CallID callID {INVALID_CALL_ID};
    MethodID methodID {INVALID_METHOD_ID};
    void hton() {
        serviceUUID = htonll(serviceUUID);
        serviceID = htonl(serviceID);
        callID = htonl(callID);
        methodID = htonl(methodID);
    }
    void ntoh() {
        serviceUUID = ntohll(serviceUUID);
        serviceID = ntohl(serviceID);
        callID = ntohl(callID);
        methodID = ntohl(methodID);
    }
};

struct RpcCallRetHeader {
    ServiceID serviceID {INVALID_SERVICE_ID};
    CallID callID {INVALID_CALL_ID};
    ErrorID errorID {0};
    void hton() {
        serviceID = htonl(serviceID);
        callID = htonl(callID);
        errorID = htonl(errorID);
    }
    void ntoh() {
        serviceID = ntohl(serviceID);
        callID = ntohl(callID);
        errorID = ntohl(errorID);
    }
};

struct RpcCallHeader {
    RpcMsgHeader header;
    RpcCallRequestHeader callHeader;
    void hton() {
        header.hton();
        callHeader.hton();
    }
    void ntoh() {
        header.ntoh();
        callHeader.ntoh();
    }
};

struct RpcRetHeader {
    RpcMsgHeader header;
    RpcCallRetHeader retHeader;
    void hton() {
        header.hton();
        retHeader.hton();
    }
    void ntoh() {
        header.ntoh();
        retHeader.ntoh();
    }
};

#if defined(_WIN32) || defined(_WIN64)
    #pragma pack(pop)
#else
    #pragma pack()
#endif /* defined(_WIN32) || defined(_WIN64) */

}
